package com.pocket.insight.base.sql.factory;

import com.pocket.insight.base.exception.BizException;
import com.pocket.insight.base.sql.Dialect;
import com.pocket.insight.base.sql.Vendor;
import com.pocket.insight.base.sql.constant.ConnectionTypeConstant;
import com.pocket.insight.base.sql.vendor.WritableVendor;
import com.pocket.insight.entity.Connection;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 描述：
 *
 * @author zhaozhile
 */
public class VendorFactory {

    private static final Logger logger = LoggerFactory.getLogger(VendorFactory.class);

    static final        Map<String, Vendor>              INITIALIZED_VENDORS   = new HashMap<>();
    public static final Set<String>                      WRITABLE_VENDOR_TYPES = new HashSet<>();
    public static final Map<String, Map<String, String>> type2QuoteChar        = new HashMap<>();


    static {
        for (Field f : ConnectionTypeConstant.class.getDeclaredFields()) {
            if (Modifier.isStatic(f.getModifiers()) && f.getType() == String.class) {
                try {
                    String type = (String) f.get(null);
                    String stdType = ConnectionTypeConstant.TYPE_TO_STD_TYPE.getOrDefault(type, type);
                    if (!INITIALIZED_VENDORS.containsKey(stdType)) {
                        INITIALIZED_VENDORS.put(stdType, initializeVendor(stdType));
                    }
                    Vendor vendor = INITIALIZED_VENDORS.get(stdType);
                    if (vendor instanceof WritableVendor) {
                        WRITABLE_VENDOR_TYPES.add(type);
                        WRITABLE_VENDOR_TYPES.add(stdType);
                    }
                    Map<String, String> dialectQuoteChar = getDialectQuoteChar(stdType);
                    if (dialectQuoteChar != null) {
                        type2QuoteChar.put(type, dialectQuoteChar);
                        type2QuoteChar.put(stdType, dialectQuoteChar);
                    }
                    logger.trace("init {} done", type);
                } catch (IllegalAccessException e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
        for (Map.Entry<String, Vendor> e : INITIALIZED_VENDORS.entrySet()) {
            try {
                logger.trace("initialize {} for {}", ((Vendor) e.getValue()).getClass().getName(), e.getKey());
            } catch (Exception ex) {
                logger.trace(ex.getMessage());
            }
        }
    }

    private static Map<String, String> getDialectQuoteChar(String type) {
        String className = "com.pocket.insight.base.sql.dialect." + StringUtils.capitalize(type) + "Dialect";
        Dialect dialect = null;
        try {
            Class<Dialect> dialectClass = (Class<Dialect>) ClassUtils.forName(className, Dialect.class.getClassLoader());
            dialect = dialectClass.newInstance();
            Map<String, String> ret = new HashMap<>();
            ret.put("left", dialect.getLeftQuoteChar());
            ret.put("right", dialect.getRightQuoteChar());
            return ret;
        } catch (Exception e) {
            logger.error("不支持的数据库类型：{}, ErrorMessage：{}", type, e.getMessage(), e);
            return null;
        }
    }

    private static Vendor initializeVendor(String type) {
        String className = "com.pocket.insight.base.sql.vendor." + StringUtils.capitalize(type) + "Vendor";
        Vendor vendor = null;
        try {

            Class<?> vendorClass = ClassUtils.forName(className, Vendor.class.getClassLoader());

            vendor = (Vendor) vendorClass.getConstructor(new Class[0]).newInstance(new Object[0]);
            logger.debug("init {} vendor, return {}", type, vendor);
        } catch (Exception e) {
            logger.error("不支持的数据库类型：{}, ErrorMessage：{}", type, e.getMessage(), e);
        }
        return vendor;
    }


    /**
     * 获取数据连接对应类型的 Vendor 处理器
     * @param connectionOptions
     * @return
     */
    public static Vendor createVendor(Connection.ConnectionOptions connectionOptions) {
        try {
            String type = connectionOptions.fetchRealType();
            logger.debug("获取vendor适配器，type: {} ; host: {}; port: {}; user:{}; db: {};", type, connectionOptions.getHost(), connectionOptions.getPort(), connectionOptions.getUsername(), connectionOptions.getDatabase());
            String stdType = ConnectionTypeConstant.TYPE_TO_STD_TYPE.getOrDefault(type, type);
            Vendor vendor = (Vendor) SerializationUtils.clone((Serializable) INITIALIZED_VENDORS.get(stdType));
            vendor.setOptions(connectionOptions);
            return vendor;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return null;
        }
    }

    public static Vendor createVendor(String vendorName) {
        String stdType = ConnectionTypeConstant.TYPE_TO_STD_TYPE.getOrDefault(vendorName, vendorName);
        if (!INITIALIZED_VENDORS.containsKey(stdType)) {
            throw new BizException("ERROR_BAD_PARAMETER: " + vendorName);
        }
        return (Vendor)SerializationUtils.clone((Serializable)INITIALIZED_VENDORS.get(stdType));
    }



    public static Vendor createQueryVendor(Connection.ConnectionOptions connectionOptions) {
        Vendor vendor = createVendor(connectionOptions);
        return vendor;
    }
}
